// We want to 


#include "PoissonDisks.h"
#include "Point.h"
#include <cstdlib>
#include <list>
#include <math.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

using namespace std;

PoissonDisks::PoissonDisks(int numDisks, double minDist, int maxX, int maxY)
{

    currNum = 0;
    maxDisks = numDisks;
    minDistance = minDist;
    width = maxX;
    height = maxY;

      /* ***** We might set a bound on numDisks. A conservative POV is to treat each disk
     * as a square instead of a circle to simplify assumptions. Using the
     * Pigeon Hole Principle, we know that the max number squares of side length
     * 2*minDist is the floor of (maxX * maxY)/(4*minDist*minDist). This is
     * assuming that the packing is perfect. To be ulta conservative, we can divide
     * this number by 2, just in case :)
     *
     *  if(numDisks > ((maxX * maxY) / (8*minDist*minDist)))
     *      maxDisks = ((maxX * maxY) / (8*minDist*minDist));
     *  else
     *      maxDisks = numDisks;
     *
     * However, for the moment, I've taken the approach to limit the number of
     * attempts of disk generation. I froze my computer like 5 times when I
     * tried impossible values of maxDisks...
     */


    // Add the boundary points
    Point *p1 = new Point(0, 0);
    Point *p2 = new Point(0, maxY);
    Point *p3 = new Point(maxX, 0);
    Point *p4 = new Point(maxX, maxY);
    Point *p5 = new Point((int) (maxX/2), (int) (maxY/2));

    allPoints.push_back(p1);
    allPoints.push_back(p2);
    allPoints.push_back(p3);
    allPoints.push_back(p4);
    allPoints.push_back(p5);
    currNum = 5;


    this->generateDisks();

}

void PoissonDisks::generateDisks()
{
    srand( time(NULL) );

    // ***** Lets put a cap on the maximum number attempts to generate disks.
    // Otherwise, this may cause infinite loops otherwise.
    unsigned int attempts = 0;

    // I found, through trial and error, that width*height*maxDisks*10 is a good
    // number to limit the attemps, if maxDisks is large. Anything bigger didn't
    // really change the mean number of disks generated.
    while (currNum < maxDisks && attempts < (unsigned int)(width*height*2))
    {

        int randX = (int) (rand() % width);
        int randY = (int) (rand() % height);
        Point * newPoint = new Point(randX, randY);

        if( this->farEnough(newPoint) )
        {
            // add point
            allPoints.push_back(newPoint);
            currNum++;

        } else {
            newPoint->~Point();
        }

        attempts++;
    }
	allPoints.resize((currNum));

    // For debugging
    // cout << "Made " << attempts << " attempts; created "<< currNum <<" points." << endl;
}

bool PoissonDisks::farEnough(Point * newP)
{
    bool check = true;

    if( !(allPoints.empty()) )
    {
        // iterate through all the points and check if distance from the new
        // point and every point <= minDist
        vector<Point*>::iterator iter;

        for(iter = allPoints.begin(); iter != allPoints.end(); iter++)
        {
            if( this->distance(newP, *iter) <= minDistance)
            {
                check = false;
            }

        }
    }

    return check;

}

double PoissonDisks::distance(Point * p1, Point * p2)
{
    double x1, x2, y1, y2;

    x1 = (double) p1->getX();
    x2 = (double) p2->getX();
    y1 = (double) p1->getY();
    y2 = (double) p2->getY();

    // The distance formula
    return (sqrt( ((x2-x1)*(x2-x1)) + ((y2-y1)*(y2-y1)) ));
}

vector<Point*> PoissonDisks::getPoints()
{
    // ***** Hmm... watch out, I don't know if I'm returning a copy, or the actual
    // value.
    return allPoints;
}

// For debugging: a function to print out all the points generated, along
// with the point's X and Y coordinates.
void PoissonDisks::printPoints()
{

    vector<Point*>::iterator iter;
    Point * curr;

    // Go through each column, from the top
    for(int y = height-1; y >=0; y--)
    {
        //Comment each line to make sure it doesn't get rejected by as .mz file
        cout << "#";
        // Go through each row, from the left
        for(int x = 0; x < width ; x++)
        {

            bool exist = false;

            // If the point exists, print x, else print -
            for(iter = allPoints.begin(); iter != allPoints.end(); iter++)
            {
                curr = * iter;
                int xi = curr->getX();
                int yi = curr->getY();

                if( xi == x && yi == y )
                    exist = true;
            }

            if(exist)
                cout << "x";
            else
                cout << "-";
        }

        cout << endl;
    }

}
